package com.mischa.atc;

/**
 * Class representing a physical aircraft, with properties such as 
 * location, speed and heading.
 * 
 * @author Mischa Spelt

 */

public class Aircraft 
{
	private Coordinate _position;
	
	private final static float BANK_ANGLE = (float) Math.toRadians(30); 
	private Heading _heading;
	private Heading _targetHeading;
	private int _turningDirection;
	
	private int _speed;	
	
	private final static float CLIMB_SPEED = 2000; // ft per minute
	private int _targetAltitude;
	private int _altitude;
	
	
	/**
	 * Default constructor; creates a new stationary aircraft at the origin 
	 * and on the ground, pointing to the north. 
	 */
	public Aircraft()
	{
		_position = new Coordinate();
		_heading = new Heading();
		_targetHeading = new Heading();
	}
	
	/**
	 * Gets the position of the aircraft 
	 * 
	 * @return 
	 * 		the position in nautical miles
	 */
	public Coordinate getPosition()
	{
		return new Coordinate(_position.getX(), _position.getY());
	}
		
	/**
	 * Gets the altitude of the aircraft.
	 * 
	 * @return
	 * 		the altitude in 100s of feet
	 */
	public int getAltitude()
	{		
		return altitudeIn100sOfFeet(_altitude);		
	}
	
	public int getClearedAltitude() {
		return altitudeIn100sOfFeet(_targetAltitude);
	}
	
	private int altitudeIn100sOfFeet(int altitude)
	{
		return ((int)Math.rint(altitude / 100.00) * 100);
	}

	public void setAltitude(int altitude) {
		if(altitude < 0)
		{
			throw new IllegalArgumentException();
		}
		_altitude = altitude;
		_targetAltitude = altitude;
	}
		
	public void maintainAltitude(int targetAltitude) {
		_targetAltitude = targetAltitude;
	}
	
	/**
	 * Gets the heading of the aircraft.
	 * 
	 * @return
	 * 		the current heading 
	 */	
	public Heading getHeading()
	{
		return _heading;
	}
	
	public Heading getClearedHeading()
	{
		return _targetHeading;
	}
	
	/**
	 * Sets the heading of the aircraft.
	 * 
	 * @param heading
	 * 		the new heading
	 */
	public void setHeading(float heading)
	{
		_heading.set(heading);
		_targetHeading.set(heading);
	}
	
	public void turn(float targetHeading)
	{
		// If the current heading lies between targetHeading - 180 and
		// targetHeading - or is exactly 180 degrees opposite, turn clockwise; counterclockwise otherwise
		if (_heading.liesBetweenCW(targetHeading - 180, targetHeading) || 
				_heading.equals(targetHeading - 180))
			turn(targetHeading, 1);
		else
			turn(targetHeading, -1);
	}
	
	public void turn(float targetHeading, int turningDirection)
	{
		_targetHeading = new Heading(targetHeading);
		_turningDirection = (int) Math.signum(turningDirection);
	}
			
	/**
	 * Gets the speed of the aircraft.
	 * More precisely, gets the true air speed (i.e. relative to the ground)
	 * in knots.
	 * 
	 * @return
	 * 		the KTAS in knots as an integer
	 */
	public int getSpeed()
	{
		return Math.round(_speed);
	}	
	
	/**
	 * Sets the speed of the aircraft.
	 * 
	 * @param speed
	 * 		the new KTAS (may be fractional as a result of calculation)
	 */
	public void setSpeed(float speed)
	{
		if(speed < 0)
			throw new IllegalArgumentException("Speed must be non-negative.");
		_speed = Math.round(speed);
	}
	
	public void stepTime(float seconds)
	{
		float hours = seconds / 3600;
		// Update the position
		_position.setX(_position.getX() + _speed * hours * (float)Math.sin(_heading.getRadians()));
		_position.setY(_position.getY() + _speed * hours * (float)Math.cos(_heading.getRadians()));
		// Update the heading, when turning		
		// 		Uses formula at http://www.flightlearnings.com/rate-of-turn/437/
		if(!_targetHeading.equals(_heading) && _turningDirection != 0)
		{
			float turnAngle = (float) (1091 * Math.tan(BANK_ANGLE) / _speed) * seconds;
			if(Math.abs(_targetHeading.get() - _heading.get()) < turnAngle)
			{
				_heading.set(_targetHeading.get());
				_turningDirection = 0;
			}
			else
			{
				_heading.add(_turningDirection * turnAngle);
			}			
		}
		
		// Update the altitude, when climbing
		if(_targetAltitude != _altitude) 
		{
			int altitudeChange = (int) (CLIMB_SPEED * (seconds / 60));
			if(Math.abs(_altitude - _targetAltitude) < altitudeChange)
			{				
				// Can go 25 ft above or below assigned altitude
				_altitude = _targetAltitude + (int) (50 * (Math.random() - 0.5));
			}
			else
			{
				_altitude += (_altitude < _targetAltitude ? 1 : -1) * altitudeChange;
			}
		}
	}


}
